package storage

import (
	"github.com/ipld/go-ipld-prime"
	"github.com/ipld/go-ipld-prime/codec/dagcbor"
	"github.com/ipld/go-ipld-prime/datamodel"
	"github.com/ipld/go-ipld-prime/fluent/qp"
	"github.com/ipld/go-ipld-prime/node/basicnode"
	"github.com/tokentransfer/node/core"
)

type IpldChunkBinary struct {
	t int64
}

func (cb *IpldChunkBinary) getType() int64 {
	return cb.t
}

func (cb *IpldChunkBinary) marshalEntry(e *chunkEntry) ([]byte, error) {
	node, err := qp.BuildMap(basicnode.Prototype.Any, -1, func(ma datamodel.MapAssembler) {
		qp.MapEntry(ma, "name", qp.String(e.name))
		qp.MapEntry(ma, "data", qp.Bytes(e.data))
		qp.MapEntry(ma, "chunks", qp.List(int64(len(e.chunks)), func(la datamodel.ListAssembler) {
			for _, c := range e.chunks {
				key := c.key
				pos := c.nextPos
				qp.ListEntry(la, qp.Map(2, func(mi datamodel.MapAssembler) {
					qp.MapEntry(mi, "key", qp.Bytes(key[:]))
					qp.MapEntry(mi, "nextPos", qp.Int(pos))
				}))
			}
		}))
		qp.MapEntry(ma, "refs", qp.Int(int64(e.refs)))
	})
	if err != nil {
		return nil, err
	}
	data, err := ipld.Encode(node, dagcbor.Encode)
	if err != nil {
		return nil, err
	}
	return data, nil
}

func (cb *IpldChunkBinary) unmarshalEntry(e *chunkEntry, data []byte) error {
	node, err := ipld.Decode(data, dagcbor.Decode)
	if err != nil {
		return err
	}
	e.name = GetStringFromNode(node, "name")
	if data, err := GetBytesFromNode(node, "data"); err != nil {
		return err
	} else {
		e.data = data
	}
	list := GetListFromNode(node, "chunks")
	if list != nil {
		chunks := make([]chunkOffset, 0)
		for !list.Done() {
			c := chunkOffset{}
			_, item, err := list.Next()
			if err != nil {
				return err
			}
			k, err := GetBytesFromNode(item, "key")
			if err != nil {
				return err
			}
			c.key = core.Key(k)
			c.nextPos = GetIntFromNode(item, "nextPos")

			chunks = append(chunks, c)
		}
		e.chunks = chunks
	}
	e.refs = int(GetIntFromNode(node, "refs"))
	return nil
}

func (cb *IpldChunkBinary) marshalItem(e *chunkItem) ([]byte, error) {
	node, err := qp.BuildMap(basicnode.Prototype.Any, -1, func(ma datamodel.MapAssembler) {
		qp.MapEntry(ma, "name", qp.String(e.name))
		qp.MapEntry(ma, "key", qp.Bytes(e.key[:]))
		qp.MapEntry(ma, "group", qp.Bool(e.group))
	})
	if err != nil {
		return nil, err
	}
	data, err := ipld.Encode(node, dagcbor.Encode)
	if err != nil {
		return nil, err
	}
	return data, nil
}

func (cb *IpldChunkBinary) unmarshalItem(e *chunkItem, data []byte) error {
	node, err := ipld.Decode(data, dagcbor.Decode)
	if err != nil {
		return err
	}
	e.name = GetStringFromNode(node, "name")

	if k, err := GetBytesFromNode(node, "key"); err != nil {
		return err
	} else {
		e.key = core.Key(k)
	}

	e.group = GetBooleanFromNode(node, "group")
	return nil
}

func GetStringFromNode(n datamodel.Node, p string) string {
	n, err := n.LookupByString(p)
	if err != nil {
		return ""
	}
	s, err := n.AsString()
	if err != nil {
		return ""
	}
	return s
}

func GetIntFromNode(n datamodel.Node, p string) int64 {
	n, err := n.LookupByString(p)
	if err != nil {
		return 0
	}
	i, err := n.AsInt()
	if err != nil {
		return 0
	}
	return i
}

func GetBooleanFromNode(n datamodel.Node, p string) bool {
	n, err := n.LookupByString(p)
	if err != nil {
		return false
	}
	b, err := n.AsBool()
	if err != nil {
		return false
	}
	return b
}

func GetBytesFromNode(n datamodel.Node, p string) ([]byte, error) {
	n, err := n.LookupByString(p)
	if err != nil {
		return nil, err
	}
	data, err := n.AsBytes()
	if err != nil {
		return nil, nil
	}
	return data, nil
}

func GetListFromNode(n datamodel.Node, p string) datamodel.ListIterator {
	n, err := n.LookupByString(p)
	if err != nil {
		return nil
	}
	return n.ListIterator()
}
